/*
 * @Author: thepoy
 * @Email: thepoy@163.com
 * @File Name: store.go
 * @Created: 2021-05-07 21:13:19
 * @Modified: 2021-05-08 13:48:04
 */

package es

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"strings"

	"github.com/elastic/go-elasticsearch/v8"
	"github.com/elastic/go-elasticsearch/v8/esapi"
)

// SearchResults es 查询响应
type SearchResults struct {
	Total int    `json:"total"`
	Hits  []*Hit `json:"hits"`
}

// Hit 查询响应中的单条结果
type Hit struct {
	Document
	Sort       []interface{} `json:"sort"`
	Highlights *struct {
		Title       []string `json:"title"`
		Content     []string `json:"content"`
		CreatedDate []string `json:"created_date"`
	} `json:"highlights,omitempty"`
}

// StoreConfig 存储配置
type StoreConfig struct {
	Client    *elasticsearch.Client
	IndexName string
}

// Store 存储结构体，用来索引和查询文档
type Store struct {
	es        *elasticsearch.Client
	indexName string
}

// NewStore 创建一个新的 Store 实例
func NewStore(c StoreConfig) (*Store, error) {
	indexName := c.IndexName
	if indexName == "" {
		indexName = "juejin-hot"
	}

	s := Store{
		es:        c.Client,
		indexName: indexName,
	}

	return &s, nil
}

// CreateIndex 用给定的格式创建索引
func (s *Store) CreateIndex(mapping string) error {
	res, err := s.es.Indices.Create(
		s.indexName,
		s.es.Indices.Create.WithBody(strings.NewReader(mapping)),
	)
	if err != nil {
		return err
	}

	if res.IsError() {
		return fmt.Errorf("Error: %s", res)
	}

	return nil
}

// Create 创建新文档到 Store 中
func (s *Store) Create(data map[string]interface{}) error {
	payload, err := json.Marshal(data)
	if err != nil {
		return err
	}

	ctx := context.Background()
	res, err := esapi.CreateRequest{
		Index:      s.indexName,
		DocumentID: data["id"].(string),
		Body:       bytes.NewReader(payload),
	}.Do(ctx, s.es)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	if res.IsError() {
		var e map[string]interface{}
		if err := json.NewDecoder(res.Body).Decode(&e); err != nil {
			return err
		}
		err := e["error"].(map[string]interface{})
		return fmt.Errorf("[%s] %s: %s", res.Status(), err["type"], err["reason"])
	}

	return nil
}

// Exists 当 id 对应的文档在 Store 中存在时，返回 true
func (s *Store) Exists(id string) (bool, error) {
	res, err := s.es.Exists(s.indexName, id)
	if err != nil {
		return false, err
	}

	switch res.StatusCode {
	case 200:
		return true, nil
	case 404:
		return false, nil
	default:
		return false, fmt.Errorf("[%s]", res.Status())
	}
}

// Update 更新指定 id 的文档
func (s *Store) Update(id string, newData map[string]string) (bool, error) {
	body := buildBody(newData)
	res, err := s.es.Update(s.indexName, id, body)
	if err != nil {
		return false, err
	}

	fmt.Println(res)

	switch res.StatusCode {
	case 200:
		return true, nil
	case 404:
		return false, nil
	default:
		return false, fmt.Errorf("[%s]", res.Status())
	}
}

// Delete 删除指定 id 的文档
func (s *Store) Delete(id string) error {
	res, err := s.es.Delete(s.indexName, id)
	if err != nil {
		return err
	}

	switch res.StatusCode {
	case 200:
		return nil
	case 404:
		return fmt.Errorf("没找到 id=%s 的文档", id)
	default:
		return fmt.Errorf("未知错误，状态码 [%s]", res.Status())
	}
}

func buildBody(newData map[string]string) io.Reader {
	var b strings.Builder

	data, err := json.Marshal(newData)
	if err != nil {
		panic(err)
	}

	b.WriteString("{\n")
	b.WriteString("\"doc\": " + string(data))
	b.WriteString("\n}")

	return strings.NewReader(b.String())
}

const searchAll = `
    "query" : { "match_all" : {} },
    "size" : 25,
    "sort" : { "id" : "desc", "_doc" : "asc" }`

const searchMatch = `
    "query" : {
        "multi_match" : {
            "query" : %q,
            "fields" : ["title^100", "content^100", "created_date"],
            "operator" : "and",
            "type":"phrase"
        }
    },
    "highlight" : {
        "fields" : {
            "title" : { "number_of_fragments" : 0 },
            "content" : { "number_of_fragments" : 3, "fragment_size" : 25 },
            "created_date" : { "number_of_fragments" : 0 }
        }
    },
    "size" : 25,
    "sort" : [ { "_score" : "desc" }, { "_doc" : "asc" } ]`
